package com.ck.filter;


import com.alibaba.fastjson.JSON;
import com.ck.cache.RedisCacheDao;
import com.ck.constant.CacheConstant;
import com.ck.constant.ClientConstant;
import com.ck.constant.RequestConstant;
import com.ck.jwt.JwtUtil;
import com.ck.model.common.User;
import com.ck.util.AjaxUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 登录过滤器
 *
 * @author dxy
 * @date 2018/7/5 12:39
 */
public class LoginFilter implements Filter {
    public static Logger logger = LoggerFactory.getLogger(LoginFilter.class);

    private static final String LOGIN = "/web/login";

    private static final String DO_LOGIN = "/web/doLogin";

    private static final String LOGOUT = "/web/logout";

    private static final String SIMULATOR_PAGE_URI = "/cpProgram/toSimulatorPage";

    private static final String METHOD_OPTIONS = "OPTIONS";

    private static final String CLIENT_REQUEST_PRE = "/client";

    private static final String METHOD_POST = "POST";

    /**
     * 监控系统需要放开的url
     */
    private static List<String> monitorUrl;

    /**
     * 静态资源后缀名称
     */
    private static List<String> staticResourceSuffix;

    /**
     * 本系统url白名单
     */
    private static List<String> sysPassUrl;

    static {
        monitorUrl = new ArrayList<>();
        monitorUrl.add("/health");
        monitorUrl.add("/status");
        monitorUrl.add("/actuator");
        monitorUrl.add("/info");
        monitorUrl.add("/env");
        monitorUrl.add("/metrics");
        monitorUrl.add("/httptrace");
        monitorUrl.add("/auditevents");
        monitorUrl.add("/threaddump");
        monitorUrl.add("/heapdump");
        monitorUrl.add("/jolokia");
        monitorUrl.add("/beans");
        monitorUrl.add("/conditions");
        monitorUrl.add("/configprops");
        monitorUrl.add("/scheduledtasks");
        monitorUrl.add("/mappings");
        monitorUrl.add("/loggers");

        staticResourceSuffix = new ArrayList<>();
        staticResourceSuffix.add(".js");
        staticResourceSuffix.add(".css");
        staticResourceSuffix.add(".scss");
        staticResourceSuffix.add(".img");
        staticResourceSuffix.add(".jpg");
        staticResourceSuffix.add(".jpeg");
        staticResourceSuffix.add(".png");

        sysPassUrl = new ArrayList<>();
        sysPassUrl.add(LOGIN);
        sysPassUrl.add(DO_LOGIN);
        sysPassUrl.add(LOGOUT);
        sysPassUrl.add(SIMULATOR_PAGE_URI);
    }

    @Autowired
    private RedisCacheDao redisCacheDao;

    @Override
    public void init(FilterConfig filterConfig) {
        ServletContext servletContext = filterConfig.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        redisCacheDao = (RedisCacheDao) ctx.getBean("redisCacheDao");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        if (METHOD_OPTIONS.equals(httpRequest.getMethod())) {
            //跨域设置
            setCross(httpResponse);
            chain.doFilter(httpRequest, httpResponse);
            return;
        }
        //上下文路径
        String contextPath = httpRequest.getContextPath();
        String requestURI = httpRequest.getRequestURI();
        //放行静态资源
        if (isStaticResource(requestURI)) {
            chain.doFilter(httpRequest, httpResponse);
            return;
        }
        //本系统直接放行的url
        if (isSysPassUrl(requestURI, contextPath)) {
            chain.doFilter(httpRequest, httpResponse);
            return;
        }
        // TODO 无法穷举URI,故无法获取更多的监控信息，spring-boot-admin监控请求,直接放行
        if (isMonitorUrl(requestURI, contextPath)) {
            chain.doFilter(httpRequest, httpResponse);
            return;
        }
        boolean isLogin = false;
        //验证用户是否登录
        String token = httpRequest.getHeader(ClientConstant.HEADER_TOKEN);
        if (StringUtils.isNotBlank(token)) {
            Object userRedis = redisCacheDao.getCache(CacheConstant.CK_TOKEN_CACHE_NAME, token);
            if (null != userRedis) {
                User u = JSON.parseObject(String.valueOf(userRedis), User.class);
                boolean isTokenOk = JwtUtil.verify(token, u.getUserName(), u.getPassword());
                if (isTokenOk) {
                    HttpSession session = httpRequest.getSession();
                    session.setAttribute(RequestConstant.SESSION_KEY_USER, u);
                    isLogin = true;
                }
            }
        } else {
            Object user = httpRequest.getSession().getAttribute(RequestConstant.SESSION_KEY_USER);
            if (user != null) {
                isLogin = true;
            }
        }
        if (isLogin) {
            chain.doFilter(httpRequest, httpResponse);
        } else {
            if (isProgramClientRequest(contextPath, requestURI) && httpRequest.getMethod().equalsIgnoreCase(METHOD_POST)) {
                //未登录，若是节目编辑客户端的post请求则返回未登录提示
                AjaxUtils.writeNeedLoginResultForAjax(httpResponse);
            } else {
                if (AjaxUtils.isAjax(httpRequest)) {
                    AjaxUtils.writeNeedLoginResultForAjax(httpResponse);
                } else {
                    httpResponse.sendRedirect(contextPath + LOGIN);
                }
            }
        }
        return;

    }

    @Override
    public void destroy() {

    }

    /**
     * 判断是否是节目编辑客户端
     *
     * @param uri
     * @return
     */
    private boolean isProgramClientRequest(String contextPath, String uri) {
        if (StringUtils.isBlank(uri)) {
            return false;
        }
        return uri.startsWith(contextPath + CLIENT_REQUEST_PRE);
    }

    /**
     * 判断是否是监控系统需要放开的url
     *
     * @param requestURI  请求的uri
     * @param contextPath 上下文
     * @return 是返回true, 反之false
     */
    private boolean isMonitorUrl(String requestURI, String contextPath) {
        for (String aMonitorUrl : monitorUrl) {
            if (requestURI.startsWith(contextPath + aMonitorUrl)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否通过
     *
     * @param requestURI  请求
     * @param contextPath 上下文路径
     * @return
     */
    private boolean isSysPassUrl(String requestURI, String contextPath) {
        for (String url : sysPassUrl) {
            if (requestURI.startsWith(contextPath + url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否静态资源请求
     *
     * @param requestURI 请求
     * @return
     */
    private boolean isStaticResource(String requestURI) {
        for (String suffix : staticResourceSuffix) {
            if (requestURI.endsWith(suffix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置跨域
     *
     * @param response 响应
     */
    private void setCross(HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
        response.addHeader("Access-Control-Allow-Headers",
                "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,token");
        response.setStatus(HttpStatus.OK.value());
    }
}
